package com.zyb.lmbackend.classifier;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zyb.lmbackend.llm.model.ClsResult;
import jakarta.annotation.PostConstruct;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 规则兜底分类器：当 LLM 输出异常或超时无法使用时，
 * 基于关键词字典进行简单匹配打分，产出近似的分类结果。
 */
@Component
@RequiredArgsConstructor
public class RuleFallbackClassifier {

    private final ObjectMapper objectMapper;
    private Map<String, List<String>> dict = new HashMap<>();

    /** 启动后从类路径加载关键词字典（rules/keywords.json）。 */
    @PostConstruct
    public void load() {
        try (InputStream is = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("rules/keywords.json")) {
            if (is == null) return;
//            去读取提前写好的keywords里面设置好屏幕，触屏，笔，物流等问题的关键词，在spring的bean创建前先把这个手动定义的关键词放入字典
            dict = objectMapper.readValue(is, new TypeReference<Map<String, List<String>>>() {});
        } catch (Exception ignored) {
        }
    }

    /**
     * 对单条文本做兜底分类。
     * 简单规则：命中关键词累加分，包含否定词略加分；分数不足阈值则归 OTHER。
     */
    public ClsResult classifyOne(String detail, int index) {
        double best = 0.0;
        String bestCode = "OTHER";
        String reason = null;
        String text = Optional.ofNullable(detail).orElse("");
        boolean neg = text.contains("不") || text.contains("不能") || text.contains("不了") || text.contains("无");
        for (Map.Entry<String, List<String>> e : dict.entrySet()) {//从keywords中提取出来的字典取出和这次无法被大模型直接分类的进行打分
            String code = e.getKey();
            double score = 0.0;
            String hit = null;
//            先把一种分类规则进行遍历，看当前退货理由和这种分类理由能打多少分
            for (String kw : e.getValue()) {

                if (kw != null && !kw.isBlank() && text.contains(kw)) {
                    score += 1.0;
                    if (hit == null) hit = kw;
                }
            }
//            有不字再加0.5分
            if (neg) score += 0.5;
//            总结出这种字典和当前退货理由的分数后，如果这是之前遍历的字典中最高分，也是最匹配的某个字典属性更新这次字典关键词就是退货理由
            if (score > best) {
                best = score;
                bestCode = code;
                reason = hit;
            }
        }
//        全部字典都没匹配过一次，这个退货理由就写成其他
        if (best < 1.5) {
            bestCode = "OTHER";
        }
//        把大模型分析不出的结果，在这里打分算出，封装理由返回对象
        return new ClsResult(index, bestCode, 0.5, reason == null ? "规则兜底" : reason);
    }
}
